/*	--- 输出集合 ---


我们介绍了Stream的几个常见操作：
	· map()
	· filter()
	· reduce()。

这些操作对【Stream】来说可以分为【2类】，

一类是【转换操作】，即把一个Stream 转换为 另一个Stream，例如map()和filter();
另一类是【聚合操作】，即对 Stream的每个元素 进行 计算，得到一个确定的结果，例如reduce()。


【区分】这【2种操作】是非常重要的，因为对于Stream来说，对其进行【转换操作】并不会触发任何计算！

我们可以做个实验：*/
import java.util.function.Supplier;
import java.util.stream.Stream;

public class Main {
	public static void main(String[] args) {
		Stream<Long> s1 = Stream.generate(new NatualSupplier());
		Stream<Long> s2 = s1.map(n -> n * n);
		Stream<Long> s3 = s2.map(n -> n - 1);

		System.out.println(s3); 
			// java.util.stream.ReferencePipeline$3@49476842
	}
}

class NatualSupplier implements Supplier<Long> {
	long n = 0;

	public Long get() {
		n++;

		return n;
	}
}


/*
因为s1是一个Long类型的序列，它的元素高达922亿个，但执行上述代码，既不会有任何内存增长，也不会有任何计算;

因为【转换操作】只是保存了【转换规则】，无论我们对【一个Stream】转换多少次，都不会有任何【实际计算】发生。

而【聚合操作】则不一样，【聚合操作】会【立刻】促使【Stream输出它的每一个元素】，并依次纳入计算，以获得最终结果。

所以，对【一个Stream】进行【聚合操作】，会触发【一系列连锁反应】：*/
Stream<Long> s1 = Stream.generate(new NatualSupplier());
Stream<Long> s2 = s1.map(n -> n * n);
Stream<Long> s3 = s2.map(n -> n - 1);
Stream<Long> s4 = s3.limit(10);

s4.reduce(0, (acc, n) -> acc + n);


public class NatualSupplier implements Supplier<Long> {
	long n = 0;

	public Long get() {
		n++; 
		return n;
	}
}


/*
我们对s4进行reduce()聚合计算，会不断请求s4输出它的每一个元素。

因为s4的上游是s3，它又会向s3请求元素，导致s3向s2请求元素，s2向s1请求元素，最终，s1从【Supplier实例】中请求到【真正的元素】，并经过一系列转换，最终被【reduce()聚合】出结果。

可见，【聚合操作】是真正需要从【Stream】请求数据的，对【一个Stream】做【聚合计算】后，结果就【不是一个Stream】，而是【一个其他的Java对象】。


========================================================


#	输出为List


reduce()只是一种聚合操作。

如果我们希望把【Stream的元素】保存到【集合】，例如【List】，

因为【List的元素】是【确定的Java对象】，因此，把【Stream】变为【List】不是【一个转换操作】，而是【一个聚合操作】，它会【强制Stream输出每个元素】。


下面的代码演示了如何将【一组String】先过滤掉【空字符串】，然后把【非空字符串】保存到【List】中：*/
import java.util.*;
import java.util.stream.*;

public class Main {
	public static void main(String[] args) {
		Stream<String> stream = Stream.of("Apple", "", null, "Pear", "", "Orange");

		List<String> list = stream.filter(s -> s != null && !s.isBlank())
								  .collect(Collectors.toList());

		System.out.println(list);
	}
}


/*
把【Stream的每个元素】收集到【List】的方法: 
	是调用 collect() 并传入 【Collectors.toList()对象】，它实际上是【一个Collector实例】，通过类似【reduce()的操作】，把【每个元素】添加到【一个收集器中（实际上是ArrayList）】。


类似的，【.collect(Collectors.toSet())】可以把【Stream的每个元素】收集到【Set】中。


----------------------------------------------------------------


#	输出为数组


把【Stream的元素】输出为【数组】 和 输出为【List】类似，我们只需要调用【toArray()方法】，并传入数组的“构造方法”：*/
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream()
					 .toArray(String[]::new);


/*
注意到传入的“构造方法”是 String[]::new，

【String[]::new的签名】实际上是 IntFunction<String[]> 定义的 String[] apply(int) ，即传入【int参数】，获得【String[]数组的返回值】。


========================================================


#	输出为Map


如果我们要把【Stream的元素】收集到【Map】中，就稍微麻烦一点。

因为对于【每个元素】，添加到【Map】时需要【key】 和 【value】， 

因此，我们要指定【2个映射函数】，分别把【元素】映射为 【key】 和 【value】：*/
import java.util.*;
import java.util.stream.*;

public class Main {
	public static void main(String[] args) {
		Stream<String> stream = Stream.of("APPL : Apple", "MSFT : Microsoft");

		Map<String, String> map = stream
								.collect(Collectors.toMap(
									// 把元素s映射为key:
									s -> s.substring(0, s.indexOf(':')),

									// 把元素s映射为value
									s -> s.substring(s.indexOf(':') + 1)
								));

		System.out.println(map);
	}
}


/*===========================================================


#	分组输出


Stream还有一个强大的【分组功能】，可以【按组输出】。

我们看下面的例子：*/
import java.util.*;
import java.util.stream.*;

public class Main {
	public static void main(String[] args) {
		List<String> list = List.of("Apple", "Banana", "Blackberry", "Cocount", "Avocado", "Cherry", "Apricots");

		Map<String, List<String>> groups = list.stream()
			.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));

		System.out.println(groups);
	}
}


/*
分组输出使用【Collectors.groupingBy()】

它需要提供【两个函数】：

一个是【分组的key】，这里使用【s -> s.substring(0, 1)】， 表示只要【首字母相同的String】分到【同一组】;

第二个是【分组的value】，这里直接使用【Collectors.toList()】，表示输出为【List】，上述代码运行结果如下：

	{
	    A=[Apple, Avocado, Apricots],
	    B=[Banana, Blackberry],
	    C=[Coconut, Cherry]
	}

可见，结果一共有3组，按"A"，"B"，"C"分组，每一组都是一个List。

-------------

假设有这样一个Student类，包含学生姓名、班级和成绩：

	class Student {
	    int gradeId; // 年级
	    int classId; // 班级
	    String name; // 名字
	    int score; // 分数
	}

如果我们有一个 Stream<Student>，利用【分组输出】，可以非常简单地按【年级】或【班级】把【Student归类】。



=============================================================


#	----- 输出集合 の 小结 -----


1. Stream可以输出为集合：

2. Stream通过collect()方法可以方便地输出为List、Set、Map，还可以分组输出。


模版: 
	.stream().collect(Collectors.toArray(String::new))

实例: */
import java.util.*;
import java.util.stream.*;

public class Main {
	public static void main(String[] args) {
		List<String> list = List.of("Apple", "Banana", "Blackberry", "Cocount", "Avocado", "Cherry", "Apricots");

		Map<String, List<String>> groups = list.stream()
			.collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));

		System.out.println(groups);
	}
}
